import { Advanced, UseVikeExtensionUiFramework } from '../../components'
import { Link, RepoLink, Warning } from '@brillout/docpress'

<Advanced>
  This page documents how to implement render modes yourself. It's an advanced task and we generally recommend simply using the <Link href="/ssr">`ssr`</Link> and <Link href="/pre-rendering#how-to-pre-render">`prerender`</Link> settings instead.
</Advanced>

For each page, you can choose a different render mode:
 - SPA
 - SSR
 - HTML-only
 - Pre-rendering (SSG)

For example, you can render an admin panel as SPA while rendering marketing pages with SSR.

> What "SPA", "SSR", "HTML-only" and "SSG" mean, and which one should be used, is explained at <Link href="/SPA-vs-SSR" />.

> The Vike boilerplates do SSR by default, which is a sensible default that works for most apps.

<UseVikeExtensionUiFramework featureName="Render Modes" />

## SPA

Rendering a page as SPA means that the page is loaded and rendered only in the browser.

To achieve that:
 1. We set `Page`'s <Link href="/meta">meta config</Link> `env` to `{ server: false, client: true }` instead of `{ server: true, client: true }`.
 2. We adapt our `onRenderHtml()` and `onRenderClient()` hooks.


**1. `Page` meta config**

By setting `Page`'s meta config `env` to `{ client: true, server: false }` we tell Vike to load `+Page.js` only in the browser.

```js
// /renderer/+config.js
// Environment: config

export default {
  meta: {
    Page: {
      env: { server: false, client: true } // SPA for all pages
    }
  }
}
```

React example (SPA + SSR):
 - Custom `renderMode` config: <RepoLink path="/examples/render-modes/renderer/+config.ts" />
 - SPA page: <RepoLink path="/examples/render-modes/pages/spa/+Page.jsx" />
 - SPA page's `renderMode` config value: <RepoLink path="/examples/render-modes/pages/spa/+renderMode.js" />
 - SSR page: <RepoLink path="/examples/render-modes/pages/ssr/+Page.jsx" />
 - SSR page's `renderMode` config value: <RepoLink path="/examples/render-modes/pages/ssr/+renderMode.js" />

Vue example: [GitHub > `AaronBeaudoin/vite-plugin-ssr-example` > `/pages/spa.page.client.vue`](https://github.com/AaronBeaudoin/vite-plugin-ssr-example/blob/main/pages/spa.page.client.vue)

> vite-plugin-ssr was the [previous name of Vike](https://vite-plugin-ssr.com/vike). Contributions welcome to fork and update the Vue example.


**2. Render hooks (SPA only)**

If we only have SPA pages, then we adapt our `onRenderHtml()` and `onRenderClient()` hooks like the following.

Client-side `onRenderClient()` hook:

```js
// /renderer/+onRenderClient.js
// Environment: browser

import { renderToDom } from 'some-ui-framework'

export { onRenderClient }

async function onRenderClient(pageContext) {
  const { Page } = pageContext
  // UI frameworks usually have two methods, such as `renderToDom()` and `hydrateDom()`.
  // Note how we use `renderToDom()` and not `hydrateDom()`.
  await renderToDom(document.getElementById('root'), Page)
}
```

> See <Link href="/hydration" /> for understanding the difference between "rendering to the DOM" and "hydrating the DOM".

We also adapt our server-side `onRenderHtml()` hook:

```js
// /renderer/+onRenderHtml.js
// Environment: server

import { escapeInject } from 'vike/server'

export { onRenderHtml }

async function onRenderHtml() {
  // Note that `div#root` is empty
  return escapeInject`<html>
    <body>
      <div id="root"></div>
    </body>
  </html>`
}
```

This is the key difference between SPA and SSR: in SPA `div#root` is empty, whereas with SSR `div#root` contains our Page's root component `pageContext.Page` rendered to HTML.

This means that, with SPA, we use our server-side `onRenderHtml()` hook to generate HTML that is just an empty shell:
the HTML doesn't contain the page's content.

For production, we usually want to <Link text="pre-render" href="/pre-rendering" /> the HTML of our SPA pages in order to remove the need for a production Node.js server.

We can also use our server-side `onRenderHtml()` hook to render `<head>`:

```js
// /renderer/+onRenderHtml.js
// Environment: server

import { escapeInject } from 'vike/server'

export { onRenderHtml }

async function onRenderHtml(pageContext) {
  const { title, description } = pageContext.config
  // Even though we load and render our page's components only in the browser,
  // we define our page's `<title>` and `<meta name="description">` on the server-side.
  return escapeInject`<html>
    <head>
      <title>${title}</title>
      <meta name="description" content="${description}" />
    </head>
    <body>
      <div id="root"></div>
    </body>
  </html>`
}
```

> `pageContext.config.title` and `pageContext.config.description` are custom settings, see <Link href="/meta#example-title-and-description" doNotInferSectionTitle />.

```js
// /pages/about/+title.js
export const title = 'About | My App'
```

```js
// /pages/about/+description.js
export const description = 'My App is ...'
```

```js
// /renderer/+config.js
export default {
  meta: {
    Page: {
      env: { server: false, client: true } // SPA for all pages
    },
    title: {
      env: { server: true, client: false }
    },
    description: {
      env: { server: true, client: false }
    }
  }
}
```


**2. Render hooks (SPA + SSR)**

If we have both SPA and SSR pages, then we adapt our `onRenderHtml()` and `onRenderClient()` hooks like this:

```js
// /renderer/+onRenderHtml.js
// Environment: server

import { escapeInject, dangerouslySkipEscape } from 'vike/server'
import { renderToHtml } from 'some-ui-framework'

export { onRenderHtml }

async function onRenderHtml(pageContext) {
  let pageHtml
  if (pageContext.Page) {
    // For SSR pages
    pageHtml = renderToHtml(pageContext.Page)
  } else {
    // For SPA pages
    pageHtml = ''
  }
  return escapeInject`<html>
    <body>
      <div id="root">${dangerouslySkipEscape(pageHtml)}</div>
    </body>
  </html>`
}
```

> If we set the `Page` meta config `env` to `{ server: false, client: true }` instead of
> `{ server: true, client: true }` in `config.js`, then `pageContext.Page` is
> only defined in the browser.

```js
// /renderer/+onRenderClient.js
// Environment: browser

import { renderToDom, hydrateDom } from 'some-ui-framework'

export { onRenderClient }

async function onRenderClient(pageContext) {
  const { Page } = pageContext
  const root = document.getElementById('root')
  if (
    // We detect SPAs by using the fact that `innerHTML === ''` for the first render of an SPA
    root.innerHTML === '' ||
    // Upon Client Routing page navigation, Vike sets `pageContext.isHydration`
    // to `false`.
    !pageContext.isHydration
  ) {
    // - SPA pages don't have any hydration steps: they need to be fully rendered.
    // - Page navigation of SSR pages also need to be fully rendered (if we use Client Routing)
    await renderToDom(root, Page)
  } else {
    // The first render of SSR pages is merely a hydration (instead of a full render)
    await hydrateDom(root, Page)
  }
}
```

React example: <Link href="/examples/render-modes/" />.
 - Server-side hook: <RepoLink path="/examples/render-modes/renderer/+onRenderHtml.jsx" />
 - Client-side hook: <RepoLink path="/examples/render-modes/renderer/+onRenderClient.jsx" />
 - Custom `renderMode` config: <RepoLink path="/examples/render-modes/renderer/+config.ts" />
 - SPA page: <RepoLink path="/examples/render-modes/pages/spa/+Page.jsx" />
 - SPA page's `renderMode` config value: <RepoLink path="/examples/render-modes/pages/spa/+renderMode.js" />
 - SSR page: <RepoLink path="/examples/render-modes/pages/ssr/+Page.jsx" />
 - SSR page's `renderMode` config value: <RepoLink path="/examples/render-modes/pages/ssr/+renderMode.js" />

Vue Example: [GitHub > `AaronBeaudoin/vite-plugin-ssr-example`](https://github.com/AaronBeaudoin/vite-plugin-ssr-example).
 - Server-side hook: [/pages/_default/_default.page.server.ts](https://github.com/AaronBeaudoin/vite-plugin-ssr-example/blob/main/pages/_default/_default.page.server.ts)
 - Client-side hook: [/pages/_default/_default.page.client.ts](https://github.com/AaronBeaudoin/vite-plugin-ssr-example/blob/main/pages/_default/_default.page.client.ts)
 - SPA page: [/pages/spa.page.client.vue](https://github.com/AaronBeaudoin/vite-plugin-ssr-example/blob/main/pages/spa.page.client.vue)
 - SSR page: [/pages/ssr.page.vue](https://github.com/AaronBeaudoin/vite-plugin-ssr-example/blob/main/pages/ssr.page.vue)

> vite-plugin-ssr was the [previous name of Vike](https://vite-plugin-ssr.com/vike). Contributions welcome to fork and update the Vue example.


## SSR

The Vike boilerplates and documentation use SSR by default.

So, if we only have SSR pages, then there is nothing for us to do: we simply follow the boilerplates/docs.

If we want to have both SSR and SPA pages, then see [the SPA section](#spa).


## Pre-rendering (SSG)

See <Link href="/pre-rendering" />.


## HTML-only

<Warning>Using modern UI frameworks (React/Vue/...) to render pages only to HTML is a novel technique and should be considered experimental.</Warning>

To render a page to HTML-only:

 1. We set `Page`'s <Link href="/meta">meta config</Link> `env` to `{ server: true, client: false }` instead of `{ server: true, client: true }`.
 1. (Optional) We define <Link href="/client">`+client.js`</Link> (e.g. to add a minimal amount of JavaScript surgically injecting bits of interactivity).


```js
// /renderer/+config.js
// Environment: config

export default {
  meta: {
    Page: {
      env: { server: true, client: false } // HTML-only for all pages
    }
  }
}
```

```js
// /pages/about/+Page.js
// Environment: server

export { Page }

function Page() {
  return <>
    <h1>HTML-only page</h1>
    <p>
      This page is rendered only to HTML. (It's not loaded/rendered in the browser-side.)
    </p>
  </>
}
```

```js
// /pages/about/+client.js
// Environment: browser

// This file represents the entire browser-side JavaScript.
// We can omit defining `+client.js` in which case the page has zero browser-side JavaScript.

console.log("I'm the page's only browser-side JavaScript line.")
```

React example:
 - Zero JavaScript: <RepoLink path="/examples/render-modes/pages/html-only/" />
 - Minimal JavaScript: <RepoLink path="/examples/render-modes/pages/html-js/" />

Vue Example: [GitHub > `AaronBeaudoin/vite-plugin-ssr-example` > `/pages/html.page.server.vue`](https://github.com/AaronBeaudoin/vite-plugin-ssr-example/blob/main/pages/html.page.server.vue)

> vite-plugin-ssr was the [previous name of Vike](https://vite-plugin-ssr.com/vike). Contributions welcome to fork and update the Vue example.


## See also

- <Link href="/clientHooks" />
